package com.mo.scopeprivilege.config.interceptor;

import com.mo.scopeprivilege.model.user.MTableInfo;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
 * description: mybatis插件拦截器
 * @author Yang Xingfu
 * @date 2019/10/23 16:57
 */
@Component
@Intercepts(@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PrivilegeInterceptor implements Interceptor {


    /**
     * 拦截方法
     * @param invocation 代理
     * @return
     * @throws Throwable 拦截处理异常
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String sql = null;
        String newSql = null;
        //  获取sql
         sql = getSqlByInvocation(invocation);
         if (sql == null) {
             return invocation.proceed();
         }

         //  处理sql
        newSql = handleSql(sql, invocation);

        resetSql2Invocation(invocation, newSql);

        return invocation.proceed();
    }



    /**
     * 插件处理方法，使用mybatis默认方法
     * @param target 代理类
     * @return 插件拦截代理对象
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 设置配置属性
     * @param properties 属性
     */
    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 获取sql
     * @param invocation 代理对象
     * @return sql
     */
    private String getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);

        return boundSql.getSql();
    }

    /**
     * description: 获取参数字段
     * @author Yang Xingfu
     * @date 2019/10/24 14:54
     * @param clazz
     * @param fieldName
     * @return java.lang.reflect.Field
     */
    public Field getField(Class<?> clazz, String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            field = null;
        }

        return field == null && !clazz.getSuperclass().equals(Object.class) ? getField(clazz.getSuperclass(), fieldName) : field;
    }

    private void resetSql2Invocation(Invocation invocation, String newSql) {

        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(),
                new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", newSql);
        args[0] = newStatement;
    }

    private MappedStatement newMappedStatement(MappedStatement statement, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(statement.getConfiguration(), statement.getId(), newSqlSource, statement.getSqlCommandType());
        builder.resource(statement.getResource());
        builder.fetchSize(statement.getFetchSize());
        builder.statementType(statement.getStatementType());
        builder.keyGenerator(statement.getKeyGenerator());
        if (statement.getKeyProperties() != null && statement.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : statement.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(statement.getTimeout());
        builder.parameterMap(statement.getParameterMap());
        builder.resultMaps(statement.getResultMaps());
        builder.resultSetType(statement.getResultSetType());
        builder.cache(statement.getCache());
        builder.flushCacheRequired(statement.isFlushCacheRequired());
        builder.useCache(statement.isUseCache());

        return builder.build();

    }

    private String handleSql(String sql, Invocation invocation) {
        int[] ids = null;
        List<MTableInfo> tableInfoList = null;

        Object[] args = invocation.getArgs();
        Object object = args[1];

        Field fIds = getField(object.getClass(), "ids");
        Field fMTableInfoList = getField(object.getClass(), "MTableInfoList");

        if (fIds != null) {
            fIds.setAccessible(true);
            try {

                ids = (int[])fIds.get(args[1]);
                fIds.setAccessible(true);
            } catch (IllegalAccessException e) {
                ids = null;
            }
        }

        if (fMTableInfoList != null) {
            fMTableInfoList.setAccessible(true);
            try {
                tableInfoList = (List<MTableInfo>) fMTableInfoList.get(args[1]);
            } catch (IllegalAccessException e) {
                tableInfoList = null;
            }
            fMTableInfoList.setAccessible(false);
        }
        System.out.println(Arrays.toString(ids));
        return "SELECT\n" +
                "     \n" +
                "    id, name\n" +
                "   \n" +
                "    FROM t_user\n" +
                "    where 1 = 1" + " and id in (1, 2); ";

    }

    class BoundSqlSqlSource implements SqlSource {

        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }


    private String getOperateType(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType commondType = ms.getSqlCommandType();
        if (commondType.compareTo(SqlCommandType.SELECT) == 0) {
            return "select";
        }
        if (commondType.compareTo(SqlCommandType.INSERT) == 0) {
            return "insert";
        }
        if (commondType.compareTo(SqlCommandType.UPDATE) == 0) {
            return "update";
        }
        if (commondType.compareTo(SqlCommandType.DELETE) == 0) {
            return "delete";
        }
        return null;

    }
}
